本文將介紹在前後分離的狀況下,後端如何與前端配合製作 Web API ,串接藍新金流服務。
至藍新測試帳號頁面註冊個人會員並登入,於會員中心⇒商店管理⇒開立商店,開立"網路商店"
開立商店完成後,回到商店資料設定,進入商店的詳細資料頁,於金流特約商店設定中,啟用的項目的支付方式於本範例只啟用 信用卡一次付清 及 WebATM,其它項目先設為不啟用
點選生成 API串接金鑰
上傳商店 Logo
後端建立 CryptoUtil.cs 類別檔,用於加解密
using System;
using System.Security.Cryptography;
using System.Text;
namespace MyProject.Security
{
/// <summary>
/// 藍新加解密 Util
/// </summary>
public class CryptoUtil
{
/// <summary>
/// 字串加密 AES
/// </summary>
/// <param name="source">加密前字串</param>
/// <param name="cryptoKey">加密金鑰</param>
/// <param name="cryptoIV">cryptoIV</param>
/// <returns>加密後字串</returns>
public static byte[] EncryptAES(byte[] source, string cryptoKey, string cryptoIV)
{
byte[] dataKey = Encoding.UTF8.GetBytes(cryptoKey);
byte[] dataIV = Encoding.UTF8.GetBytes(cryptoIV);
using (var aes = Aes.Create()) {
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = dataKey;
aes.IV = dataIV;
using (var encryptor = aes.CreateEncryptor()) {
return encryptor.TransformFinalBlock(source, 0, source.Length);
}
}
}
/// <summary>
/// 字串解密 AES
/// </summary>
/// <param name="source">解密前字串</param>
/// <param name="cryptoKey">解密金鑰</param>
/// <param name="cryptoIV">cryptoIV</param>
/// <returns>解密後字串</returns>
public static byte[] DecryptAES(byte[] source, string cryptoKey, string cryptoIV)
{
byte[] dataKey = Encoding.UTF8.GetBytes(cryptoKey);
byte[] dataIV = Encoding.UTF8.GetBytes(cryptoIV);
using (var aes = Aes.Create()) {
aes.Mode = CipherMode.CBC;
// 無法直接用 PaddingMode.PKCS7,會跳"填補無效,而且無法移除。"
// 所以改為 PaddingMode.None 並搭配 RemovePKCS7Padding
aes.Padding = PaddingMode.None;
aes.Key = dataKey;
aes.IV = dataIV;
using (var decryptor = aes.CreateDecryptor()) {
return RemovePKCS7Padding(decryptor.TransformFinalBlock(source, 0, source.Length));
}
}
}
/// <summary>
/// 加密後再轉 16 進制字串
/// </summary>
/// <param name="source">加密前字串</param>
/// <param name="cryptoKey">加密金鑰</param>
/// <param name="cryptoIV">cryptoIV</param>
/// <returns>加密後的字串</returns>
public static string EncryptAESHex(string source, string cryptoKey, string cryptoIV)
{
string result = string.Empty;
if (!string.IsNullOrEmpty(source)) {
var encryptValue = EncryptAES(Encoding.UTF8.GetBytes(source), cryptoKey, cryptoIV);
if (encryptValue != null) {
result = BitConverter.ToString(encryptValue)?.Replace("-", string.Empty)?.ToLower();
}
}
return result;
}
/// <summary>
/// 16 進制字串解密
/// </summary>
/// <param name="source">加密前字串</param>
/// <param name="cryptoKey">加密金鑰</param>
/// <param name="cryptoIV">cryptoIV</param>
/// <returns>解密後的字串</returns>
public static string DecryptAESHex(string source, string cryptoKey, string cryptoIV)
{
string result = string.Empty;
if (!string.IsNullOrEmpty(source)) {
// 將 16 進制字串 轉為 byte[] 後
byte[] sourceBytes = GetByteArray(source);
if (sourceBytes != null) {
// 使用金鑰解密後,轉回 加密前 value
result = Encoding.UTF8.GetString(DecryptAES(sourceBytes, cryptoKey, cryptoIV)).Trim();
}
}
return result;
}
/// <summary>
/// 字串加密 SHA256
/// </summary>
/// <param name="source">加密前字串</param>
/// <returns>加密後字串</returns>
public static string EncryptSHA256(string source)
{
string result = string.Empty;
using (SHA256 algorithm = SHA256.Create()) {
var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(source));
if (hash != null) {
result = BitConverter.ToString(hash)?.Replace("-", string.Empty)?.ToUpper();
}
}
return result;
}
/// <summary>
/// 將 16 進位字串轉換為 byteArray
/// </summary>
/// <param name="source">欲轉換之字串</param>
/// <returns></returns>
public static byte[] GetByteArray(string source)
{
byte[] result = null;
if (!string.IsNullOrWhiteSpace(source)) {
var outputLength = source.Length / 2;
var output = new byte[outputLength];
for (var i = 0; i < outputLength; i++) {
output[i] = Convert.ToByte(source.Substring(i * 2, 2), 16);
}
result = output;
}
return result;
}
private static byte[] RemovePKCS7Padding(byte[] data)
{
int iLength = data[data.Length - 1];
var output = new byte[data.Length - iLength];
Buffer.BlockCopy(data, 0, output, 0, output.Length);
return output;
}
}
}
前端
建立 "確認商品" 頁面,點選按鈕後將商品資料送到後端接收 API,夾帶登入的 token 用來取得購買者身份 (付款前)
後端建立接收使用者購買內容 API (付款前)
[HttpPost]
public IHttpActionResult SetChargeData(ChargeRequest chargeData)
{
// Do Something ~ (相關資料檢查處理,成立訂單加入資料庫,並將訂單付款狀態設為未付款)
// 整理金流串接資料
// 加密用金鑰
string hashKey = "填入生成的 HashKey";
string hashIV = "填入生成的 HashIV";
// 金流接收必填資料
string merchantID = "填入商店代號";
string tradeInfo = "";
string tradeSha = "";
string version = "2.0"; // 參考文件串接程式版本
// tradeInfo 內容,導回的網址都需為 https
string respondType = "JSON"; // 回傳格式
string timeStamp = ((int)(dateTimeNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds).ToString();
string merchantOrderNo = timeStamp +"_"+ "訂單ID"; // 底線後方為訂單ID,解密比對用,不可重覆(規則參考文件)
string amt = "訂單金額";
string itemDesc = "商品資訊";
string tradeLimit = "600"; // 交易限制秒數
string notifyURL = @"https://" + Request.RequestUri.Host + "NotifyURL"; // NotifyURL 填後端接收藍新付款結果的 API 位置,如 : /api/users/getpaymentdata
string returnURL = "付款完成導回頁面網址" + "/" + "訂單ID"; // 前端可用 Status: SUCCESS 來判斷付款成功,網址夾帶可拿來取得活動內容
string email = "消費者信箱"; // 通知付款完成用
string loginType = "0"; // 0不須登入藍新金流會員
// 將 model 轉換為List<KeyValuePair<string, string>>
List<KeyValuePair<string, string>> tradeData = new List<KeyValuePair<string, string>>() {
new KeyValuePair<string, string>("MerchantID", merchantID),
new KeyValuePair<string, string>("RespondType", respondType),
new KeyValuePair<string, string>("TimeStamp", timeStamp),
new KeyValuePair<string, string>("Version", version),
new KeyValuePair<string, string>("MerchantOrderNo", merchantOrderNo),
new KeyValuePair<string, string>("Amt", amt),
new KeyValuePair<string, string>("ItemDesc", itemDesc),
new KeyValuePair<string, string>("TradeLimit", tradeLimit),
new KeyValuePair<string, string>("NotifyURL", notifyURL),
new KeyValuePair<string, string>("ReturnURL", returnURL),
new KeyValuePair<string, string>("Email", email),
new KeyValuePair<string, string>("LoginType", loginType)
};
// 將 List<KeyValuePair<string, string>> 轉換為 key1=Value1&key2=Value2&key3=Value3...
var tradeQueryPara = string.Join("&", tradeData.Select(x => $"{x.Key}={x.Value}"));
// AES 加密
tradeInfo = CryptoUtil.EncryptAESHex(tradeQueryPara, hashKey, hashIV);
// SHA256 加密
tradeSha = CryptoUtil.EncryptSHA256($"HashKey={hashKey}&{tradeInfo}&HashIV={hashIV}");
// 送出金流串接用資料,給前端送藍新用
return Ok(new
{
Status = true,
PaymentData = new
{
MerchantID = merchantID,
TradeInfo = tradeInfo,
TradeSha = tradeSha,
Version = version
}
});
}
前端
將後端加密回傳的資料填入後,用表單送出到藍新處理頁面 (欄位內容設為隱藏)
<!-- 用表單送給藍新 -->
<form name='Newebpay' method='post' action='https://ccore.newebpay.com/MPG/mpg_gateway'>
<!-- 設定 hidden 可以隱藏不用給使用者看的資訊 -->
<!-- 藍新金流商店代號 -->
<input type='hidden' id='MerchantID' name='MerchantID' value='填入後端回傳的 MerchantID'>
<!-- 交易資料透過 Key 及 IV 進行 AES 加密 -->
<input type='hidden' id='TradeInfo' name='TradeInfo' value='填入後端回傳的 TradeInfo'>
<!-- 經過上述 AES 加密過的字串,透過商店 Key 及 IV 進行 SHA256 加密 -->
<input type='hidden' id='TradeSha' name='TradeSha' value='填入後端回傳的 TradeSha'>
<!-- 串接程式版本 -->
<input type='hidden' id='Version' name='Version' value='填入後端回傳的 Version'>
<!-- 直接執行送出 -->
<input type='submit' value='前往付款'>
</form>
測試用付款頁面,付款完成後會導回 ReturnURL,並同時將付款結果資料傳到 NotifyURL
後端建立藍新回傳資料欄位類別檔 NewebPayReturn.cs 及 解密資料欄位類別檔 PaymentResult.cs
namespace MyProject.Security
{
public class NewebPayReturn
{
public string Status { get; set; }
public string MerchantID { get; set; }
public string Version { get; set; }
public string TradeInfo { get; set; }
public string TradeSha { get; set; }
}
}
namespace MyProject.Security
{
public class PaymentResult
{
public string Status { get; set; }
public string Message { get; set; }
public Result Result { get; set; }
}
public class Result
{
public string MerchantID { get; set; }
public string Amt { get; set; }
public string TradeNo { get; set; }
public string MerchantOrderNo { get; set; }
public string RespondType { get; set; }
public string IP { get; set; }
public string EscrowBank { get; set; }
public string PaymentType { get; set; }
public string RespondCode { get; set; }
public string Auth { get; set; }
public string Card6No { get; set; }
public string Card4No { get; set; }
public string Exp { get; set; }
public string TokenUseStatus { get; set; }
public string InstFirst { get; set; }
public string InstEach { get; set; }
public string Inst { get; set; }
public string ECI { get; set; }
public string PayTime { get; set; }
public string PaymentMethod { get; set; }
}
}
後端建立接收付款資訊內容 API (串接藍新-付款完)
[HttpPost]
public HttpResponseMessage GetPaymentData(NewebPayReturn data)
{
// 付款失敗跳離執行
var response = Request.CreateResponse(HttpStatusCode.OK);
if (!data.Status.Equals("SUCCESS")) return response;
// 加密用金鑰
string hashKey = "填入生成的 HashKey";
string hashIV = "填入生成的 HashIV";
// AES 解密
string decryptTradeInfo = CryptoUtil.DecryptAESHex(data.TradeInfo, hashKey, hashIV);
PaymentResult result = JsonConvert.DeserializeObject<PaymentResult>(decryptTradeInfo);
// 取出交易記錄資料庫的訂單ID
string[] orderNo = result.Result.MerchantOrderNo.Split('_');
int logId = Convert.ToInt32(orderNo[1]);
// 用取得的"訂單ID"修改資料庫此筆訂單的付款狀態為 true
// 用取得的"訂單ID"寄出付款完成訂單成立,商品準備出貨通知信
return response;
}
前端
使用 ReturnURL 網址夾帶的"訂單ID"向後端取得訂單資料,並顯示於畫面告知用戶
Status=SUCCESS
來判斷付款有沒有成功